package com.open.capacity.workflow3.engine;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.open.capacity.utils.BigDecimalUtil;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NwfEngine implements NwfIEngine {

    private NwfEngineConfig config;

    private NwfScheme wfScheme;

    private Map<String, NwfNodeInfo> nodesMap;

    private NwfNodeInfo startNode;

    public NwfEngine(NwfEngineConfig nWFEngineConfig) {
        config = nWFEngineConfig;
        wfScheme = JSONObject.parseObject(config.paramConfig.scheme, new TypeReference<NwfScheme>() {
        });
        nodesMap = new HashMap<>();
        for (NwfNodeInfo node : wfScheme.nodes) {
            if (!nodesMap.containsKey(node.id)) {
                nodesMap.put(node.id, node);
            }
            if (node.type.equals("startround")) {
                startNode = node;
            }
        }
    }

    @Override
    public String getScheme() {
        return config.paramConfig.scheme;
    }

    @Override
    public NwfScheme getSchemeObj() {
        return wfScheme;
    }

    @Override
    public NwfNodeInfo getStartNode() {
        return startNode;
    }

    @Override
    public NwfNodeInfo getNode(String nodeId) {
        if (nodesMap.containsKey(nodeId)) {
            return nodesMap.get(nodeId);
        }
        return null;
    }

    @Override
    public Boolean getLines(String fromNodeId, String toNodeId, List<NwfLineInfo> list, Map<String, String> nodes) {
        boolean res = false;
        if (nodes == null) {
            nodes = new HashMap<>();
        }
        for (NwfLineInfo line : wfScheme.lines) {
            if (line.from.equals(fromNodeId)) {
                if (line.to.equals(toNodeId)) {
                    list.add(line);
                    return true;
                }
                if (!(line.to == fromNodeId) && nodesMap.get(line.to) != null && !(nodesMap.get(line.to).type.equals("endround")) && !nodes.containsKey(line.to)) {
                    nodes.put(line.to, "1");
                    res = getLines(line.to, toNodeId, list, nodes);
                }
                if (res) {
                    list.add(line);
                    return true;
                }
            }
        }
        return res;
    }

    @Override
    public List<NwfNodeInfo> getNextNodes(String nodeId, String code, List<NwfLineInfo> lineList) {
        List<NwfNodeInfo> nextNodes = new ArrayList<>();
        for (NwfLineInfo line : wfScheme.lines) {
            if (!(line.from.equals(nodeId))) {
                continue;
            }
            boolean isOk = false;
            if (StringUtils.isEmpty(line.strategy) || line.strategy.equals("1")) {
                isOk = true;
            } else {
                String[] array = line.agreeList.split(",");
                for (int i = 0; i < array.length; i++) {
                    if (array[i].equals(code)) {
                        isOk = true;
                        break;
                    }
                }
            }
            if (!isOk || !nodesMap.containsKey(line.to)) {
                continue;
            }
            nextNodes.add(nodesMap.get(line.to));
            if (StringUtils.isNotEmpty(line.operationType)) {
                switch (line.operationType) {
                    case "sql":
                        if (StringUtils.isNotEmpty(line.dbId) && StringUtils.isNotEmpty(line.strSql)) {
                            lineList.add(line);
                        }
                        break;
                    case "interface":
                        if (StringUtils.isNotEmpty(line.strInterface)) {
                            lineList.add(line);
                        }
                        break;
                    case "ioc":
                        if (StringUtils.isNotEmpty(line.iocName)) {
                            lineList.add(line);
                        }
                        break;
                }
            }
        }
        return nextNodes;
    }

    @Override
    public List<String> getPreNodes(String nodeId) {
        List<String> list = new ArrayList<>();
        for (NwfLineInfo line : wfScheme.lines) {
            if (line.to == nodeId) {
                list.add(line.from);
            }
        }
        return list;
    }

    @Override
    public Boolean isToNode(String formNodeId, String toNodeId) {
        boolean res = false;
        for (NwfLineInfo line : wfScheme.lines) {
            if (line.from == formNodeId) {
                if (line.to == toNodeId) {
                    return true;
                }
                if (line.to == formNodeId) {
                    return res;
                }
                if (nodesMap.get(line.to) == null) {
                    return res;
                }
                if (nodesMap.get(line.to).type.equals("endround")) {
                    return res;
                }
                if (isToNode(line.to, toNodeId)) {
                    return true;
                }
            }
        }
        return res;
    }

    @Override
    public NwfEngineParamConfig getConfig() {
        return config.paramConfig;
    }

    @Override
    public List<NwfNodeInfo> getNextTaskNode(NwfNodeInfo beginNode, String code, Boolean isGetAuditors, List<NwfLineInfo> lineList) {
        List<NwfNodeInfo> list = new ArrayList<>();
        List<NwfNodeInfo> nextNodes = getNextNodes(beginNode.id, code, lineList);
        Map<String, String> auditers = null;
        if (StringUtils.isNotEmpty(config.paramConfig.auditers)) {
            auditers = JSONObject.parseObject(config.paramConfig.auditers, new TypeReference<Map<String, String>>() {
            });
        }
        for (NwfNodeInfo node : nextNodes) {
            if (auditers != null && auditers.containsKey(node.id)) {
                node.auditors = new ArrayList<>();
                NwfAuditor auditor = new NwfAuditor();
                auditor.type = 3;
                auditor.auditorId = auditers.get(node.id);
                node.auditors.add(auditor);
            }
            switch (node.type) {
                case "conditionnode":
                    if (!isGetAuditors) {
                        if (calcCondition(node)) {
                            list.addAll(getNextTaskNode(node, "agree", isGetAuditors, lineList));
                        } else {
                            list.addAll(getNextTaskNode(node, "disagree", isGetAuditors, lineList));
                        }
                    } else {
                        list.addAll(getNextTaskNode(node, "agree", isGetAuditors, lineList));
                        list.addAll(getNextTaskNode(node, "disagree", isGetAuditors, lineList));
                    }
                    break;
                case "confluencenode":
                    if (!isGetAuditors) {
                        int confluenceRes = ((!(code.equals("agree"))) ? calcConfluence(node, beginNode.id, false) : calcConfluence(node, beginNode.id, true));
                        switch (confluenceRes) {
                            case 1:
                                list.addAll(getNextTaskNode(node, "agree", false, lineList));
                                break;
                            case -1:
                                list.addAll(getNextTaskNode(node, "disagree", false, lineList));
                                break;
                        }
                        node.confluenceRes = confluenceRes;
                        list.add(node);
                    }
                    break;
                case "auditornode":
                    list.add(node);
                    break;
                case "childwfnode":
                    list.add(node);
                    if (node.childType.equals("2")) {
                        list.addAll(getNextTaskNode(node, "agree", isGetAuditors, lineList));
                    }
                    break;
                case "startround":
                    list.add(node);
                    config.paramConfig.state = 1;
                    break;
                case "endround":
                    config.paramConfig.state = 2;
                    break;
                default:
                    list.add(node);
                    break;
            }
        }
        return list;
    }

    private Boolean calcCondition(NwfNodeInfo node) {
        boolean res = true;
        if (node.conditions.size() > 0) {
            for (NwfCondition condition : node.conditions) {
                if (StringUtils.isNotEmpty(condition.dbId) && StringUtils.isNotEmpty(condition.table) && StringUtils.isNotEmpty(condition.field1) && StringUtils.isNotEmpty(condition.field2)) {
                    String sql = "select " + condition.field2 + " from " + condition.table + " where " + condition.field1 + " =@processId ";
                    Map<String, Object> params = new HashMap<>();
                    params.put("processId", config.paramConfig.processId);
                    List<Map<String, Object>> resut = config.dbTableService.findData(condition.dbId, sql, params);
                    if (resut.size() > 0) {
                        String value = resut.get(0).get(0).toString();
                        if (StringUtils.isEmpty(value)) {
                            return false;
                        }
                        switch (condition.compareType) {
                            case 1:
                                if (value != condition.value) {
                                    res = false;
                                }
                                break;
                            case 2:
                                if (value == condition.value) {
                                    res = false;
                                }
                                break;
                            case 3:
                                if (BigDecimalUtil.getData(new BigDecimal(value)).compareTo(BigDecimalUtil.getData(new BigDecimal(condition.value))) <= 0) {
                                    res = false;
                                }
                                break;
                            case 4:
                                if (BigDecimalUtil.getData(new BigDecimal(value)).compareTo(BigDecimalUtil.getData(new BigDecimal(condition.value))) < 0) {
                                    res = false;
                                }
                                break;
                            case 5:
                                if (BigDecimalUtil.getData(new BigDecimal(value)).compareTo(BigDecimalUtil.getData(new BigDecimal(condition.value))) >= 0) {
                                    res = false;
                                }
                                break;
                            case 6:
                                if (BigDecimalUtil.getData(new BigDecimal(value)).compareTo(BigDecimalUtil.getData(new BigDecimal(condition.value))) > 0) {
                                    res = false;
                                }
                                break;
                            case 7:
                                if (!value.contains(condition.value)) {
                                    res = false;
                                }
                                break;
                            case 8:
                                if (value.contains(condition.value)) {
                                    res = false;
                                }
                                break;
                            case 9:
                                if (!condition.value.contains(value)) {
                                    res = false;
                                }
                                break;
                            case 10:
                                if (condition.value.contains(value)) {
                                    res = false;
                                }
                                break;
                        }
                    } else {
                        res = false;
                    }
                }
                if (!res) {
                    return res;
                }
            }
            return res;
        }
        if (StringUtils.isNotEmpty(node.conditionSql)) {
            res = false;
            String conditionSql = node.conditionSql.replace("{processId}", "#{{processId}");
            conditionSql = conditionSql.replace("{userId}", "#{userId}");
            conditionSql = conditionSql.replace("{userAccount}", "#{userAccount}");
            conditionSql = conditionSql.replace("{companyId}", "#{companyId}");
            conditionSql = conditionSql.replace("{departmentId}", "#{departmentId}");
            Map<String, Object> param = new HashMap<>();
            param.put("processId", config.paramConfig.processId);
            param.put("userId", config.paramConfig.createUser.id);
            param.put("userAccount", config.paramConfig.createUser.account);
            param.put("companyId", config.paramConfig.createUser.companyId);
            param.put("departmentId", config.paramConfig.createUser.departmentId);

            if (config.dbTableService.findData(node.dbConditionId, conditionSql, param).size() > 0) {
                res = true;
            }
        } else {
            res = true;
        }
        return res;
    }

    private int calcConfluence(NwfNodeInfo wfNodeInfo, String preNodeId, Boolean isAgree) {
        int res = 0;
        long agreeNum = config.confluenceNumInterface.getAgreeNum(config.paramConfig.processId, wfNodeInfo.id);
        long disAgreeNum = config.confluenceNumInterface.getDisAgreeNum(config.paramConfig.processId, wfNodeInfo.id);
        List<String> preNodeList = getPreNodes(wfNodeInfo.id);
        switch (wfNodeInfo.confluenceType) {
            case 1:
                if (isAgree) {
                    if (preNodeList.size() == agreeNum + 1) {
                        res = 1;
                    }
                } else {
                    res = -1;
                }
                break;
            case 2:
                if (isAgree) {
                    res = 1;
                } else if (preNodeList.size() == disAgreeNum + 1) {
                    res = -1;
                }
                break;
            case 3:
                if (isAgree) {
                    if ((new BigDecimal((agreeNum + 1) * 100 / preNodeList.size())).compareTo(new BigDecimal(wfNodeInfo.confluenceRate)) >= 0) {
                        res = 1;
                    }
                } else if (new BigDecimal((preNodeList.size() - disAgreeNum - 1) * 100 / preNodeList.size()).compareTo(new BigDecimal(wfNodeInfo.confluenceRate)) < 0) {
                    res = -1;
                }
                break;
        }
        return res;
    }
}
